/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */package gnu.jpdf;import java.awt.BasicStroke;import java.awt.Color;import java.awt.Composite;import java.awt.Font;import java.awt.FontMetrics;import java.awt.Frame;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.GraphicsConfiguration;import java.awt.Image;import java.awt.LinearGradientPaint;import java.awt.MultipleGradientPaint;import java.awt.Paint;import java.awt.Polygon;import java.awt.RadialGradientPaint;import java.awt.Rectangle;import java.awt.RenderingHints;import java.awt.RenderingHints.Key;import java.awt.Shape;import java.awt.Stroke;import java.awt.font.FontRenderContext;import java.awt.font.GlyphVector;import java.awt.geom.AffineTransform;import java.awt.geom.Area;import java.awt.geom.NoninvertibleTransformException;import java.awt.geom.PathIterator;import java.awt.geom.Point2D;import java.awt.geom.Rectangle2D;import java.awt.image.BufferedImage;import java.awt.image.BufferedImageOp;import java.awt.image.ColorModel;import java.awt.image.ImageObserver;import java.awt.image.RenderedImage;import java.awt.image.WritableRaster;import java.awt.image.renderable.RenderableImage;import java.awt.print.PageFormat;import java.io.File;import java.io.IOException;import java.io.OutputStream;import java.io.Serializable;import java.text.DecimalFormat;import java.text.DecimalFormatSymbols;import java.util.ArrayList;import java.util.Hashtable;import java.util.List;import java.util.Locale;import java.util.Map;import java.util.WeakHashMap;import java.util.logging.Level;import java.util.logging.Logger;/** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */public class PDFGraphics extends Graphics2D implements Serializable {    /**     * One degree in radians     */    private static final double degrees_to_radians = Math.PI / 180.0;    private static final int FILL = 1;    private static final int STROKE = 2;    private static final int CLIP = 3;    private static final AffineTransform IDENTITY = new AffineTransform();    private static final Stroke DEF_STROKE = new BasicStroke();    /*   * NOTE: The original class is the work of Peter T. Mount, who released it   * in the uk.org.retep.pdf package.  It was modified by Eric Z. Beard as   * follows:   * The package name was changed to gnu.pdf.   * The formatting was changed a little bit.   * This used to subclass an abstract class in a different package with   *   the same name (confusing). Now it's one concrete class.   * drawImage() was implemented   * It is still licensed under the LGPL.     */    // Implementation notes:    //    // Pages 333-335 of the PDF Reference Manual    //    // Unless absolutely required, use the moveto, lineto and rectangle    // operators to perform those actions.    // They contain some extra optimizations    // which will reduce the output size by up to half in some cases.    //    // About fill operators: For correct operation, any fill operation should    // start with closeBlock(), which will ensure any previous path is completed,    // otherwise you may find the fill will include previous items    private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH));    //JPEXS: cache for already used images    private static Map<Image, PDFImage> usedImages = new WeakHashMap<Image, PDFImage>();    private Color background;    /**     * This is true for any Graphics instance that didn't create the stream.     *     * @see #create     */    private boolean child;    private Area clip;    /**     * This holds the current clipRectangle     */    protected Rectangle clipRectangle;    private Composite composite;    private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics();    /**     * This is the current font (in Java format)     */    private Font font;    /**     * Part of the optimizer: When true, we are drawing a path.     */    private boolean inStroke;    /**     * Part of the optimizer: When true, we are within a Text Block.     */    private boolean inText;   // true if within a Text Block - see newTextBlock()    /**     * The stroke line cap code;     */    private int lineCap = 0;    /**     * The stroke line join code     */    private int lineJoin = 0;    /**     * The stroke line width     */    private float lineWidth = 1.0f;    /**     * Part of the optimizer: The last known moveto/lineto x coordinate     *     * @see #moveto     * @see #lineto     */    private float lx;             // last known moveto/lineto coordinates    /**     * Part of the optimizer: The last known moveto/lineto y coordinate     *     * @see #moveto     * @see #lineto     */    private float ly;             // last known moveto/lineto coordinates    private float miterLimit = 10.0f;    /**     * Part of the optimizer: When true, the font has changed.     */    private boolean newFont;    // true if the font changes - see newTextBlock()    /**     * This is a reference to the PDFPage we are rendering to.     */    private PDFPage page;    /**     * This is the current pen/fill color     */    private Paint paint;    /**     * This is the current font (in PDF format)     */    private PDFFont pdffont;    /**     * Part of the optimizer: This is written to the stream when the newPath()     * is called. np then clears this value.     */    private String pre_np;    // PDF space transform    private AffineTransform pTransform;    /**     * This is the PrintWriter used to write PDF drawing commands to the Stream     */    private RawPrintWriter pw;    /**     * RenderingHints     */    private RenderingHints rhints = new RenderingHints(null);    private Stroke stroke;    // Start of Graphics2D properties    private AffineTransform transform;    /**     * Part of the optimizer: The last x coordinate when rendering text     */    private float tx;             // the last coordinate for text rendering    /**     * Part of the optimizer: The last y coordinate when rendering text     */    private float ty;             // the last coordinate for text rendering    private String shading = null;    private String pattern = null;    private int shadingCount = 0;    /**     * @see Graphics2D#addRenderingHints(Map)     */    @Override    public void addRenderingHints(Map<?, ?> hints) {        rhints.putAll(hints);    }    /**     * This produces an arc by breaking it down into one or more Bezier curves.     * It is used internally to implement the drawArc and fillArc methods.     *     * @param axc X coordinate of arc centre     * @param ayc Y coordinate of arc centre     * @param width of bounding rectangle     * @param height of bounding rectangle     * @param ang1 Start angle     * @param ang2 End angle     * @param clockwise true to draw clockwise, false anti-clockwise     */    public void arc(double axc, double ayc,            double width, double height,            double ang1, double ang2,            boolean clockwise) {        double adiff;        double x0, y0;        double x3r, y3r;        boolean first = true;        // may not need this        //if( ar < 0 ) {        //ang1 += fixed_180;        //ang2 += fixed_180;        //ar = - ar;        //}        double ang1r = (ang1 % 360.0) * degrees_to_radians;        double sin0 = Math.sin(ang1r);        double cos0 = Math.cos(ang1r);        x0 = axc + width * cos0;        y0 = ayc + height * sin0;        // NB: !clockwise here as Java Space is inverted to User Space        if (!clockwise) {            // Quadrant reduction            while (ang1 < ang2) {                ang2 -= 360.0;            }            while ((adiff = ang2 - ang1) < -90.0) {                double w = sin0;                sin0 = -cos0;                cos0 = w;                x3r = axc + width * cos0;                y3r = ayc + height * sin0;                arc_add(first,                        width, height,                        x0, y0,                        x3r, y3r,                        (x0 + width * cos0),                        (y0 + height * sin0)                );                x0 = x3r;                y0 = y3r;                ang1 -= 90.0;                first = false;            }        } else {            // Quadrant reduction            while (ang2 < ang1) {                ang2 += 360.0;            }            while ((adiff = ang2 - ang1) > 90.0) {                double w = cos0;                cos0 = -sin0;                sin0 = w;                x3r = axc + width * cos0;                y3r = ayc + height * sin0;                arc_add(first,                        width, height,                        x0, y0,                        x3r, y3r,                        (x0 + width * cos0),                        (y0 + height * sin0)                );                x0 = x3r;                y0 = y3r;                ang1 += 90.0;                first = false;            }        }        // Compute the intersection of the tangents.        // We know that -fixed_90 <= adiff <= fixed_90.        double trad = Math.tan(adiff * (degrees_to_radians / 2));        double ang2r = ang2 * degrees_to_radians;        double xt = x0 - trad * width * sin0;        double yt = y0 + trad * height * cos0;        arc_add(first, width, height, x0, y0,                (axc + width * Math.cos(ang2r)),                (ayc + height * Math.sin(ang2r)),                xt, yt);    }    /**     * Used by the arc method to actually add an arc to the path Important: We     * write directly to the stream here, because this method operates in User     * space, rather than Java space.     *     * @param first true if the first arc     * @param w width     * @param h height     * @param x0 coordinate     * @param y0 coordinate     * @param x3 coordinate     * @param y3 coordinate     * @param xt coordinate     * @param yt coordinate     */    private void arc_add(boolean first,            double w, double h,            double x0, double y0,            double x3, double y3,            double xt, double yt) {        double dx = xt - x0, dy = yt - y0;        double dist = dx * dx + dy * dy;        double w2 = w * w, h2 = h * h;        double r2 = w2 + h2;        double fw = 0.0, fh = 0.0;        if (dist < (r2 * 1.0e8)) {            // JM            fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0;            fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0;        }        // The path must have a starting point        if (first) {            moveto(x0, y0);        }        double x = x0 + ((xt - x0) * fw);        double y = y0 + ((yt - y0) * fh);        x0 = x3 + ((xt - x3) * fw);        y0 = y3 + ((yt - y3) * fh);        // Finally the actual curve.        curveto(x, y, x0, y0, x3, y3);    }    /**     * This simply draws a White Rectangle to clear the area     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void clearRect(int x, int y, int w, int h) {        closeBlock();        pw.print("q 1 1 1 RG ");// save state, set colour to White        drawRect(x, y, w, h);        closeBlock("B Q");               // close fill & stroke, then restore state    }    /**     * @see Graphics2D#clip(Shape)     */    @Override    public void clip(Shape s) {        if (s == null) {            setClip(null);            return;        }        if (clip == null) {            clip = new Area(s);        } else {            clip.intersect(new Area(s));        }//      followPath(s, CLIP);    }    /**     * This extra method allows PDF users to clip to a Polygon.     *     * <p>     * In theory you could use setClip(), except that java.awt.Graphics only     * supports Rectangle with that method, so we will have an extra method.     *     * @param p Polygon to clip to     */    public void clipPolygon(Polygon p) {        closeBlock();            // finish off any existing path        polygon(p.xpoints, p.ypoints, p.npoints);        closeBlock("W");         // clip to current path        clipRectangle = p.getBounds();    }    /**     * Clips to a set of coordinates     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void clipRect(int x, int y, int w, int h) {        setClip(x, y, w, h);    }    /**     * All functions should call this to close any existing optimized blocks.     */    void closeBlock() {        closeBlock("S");    }    /**     * <p>     * This is used by code that use the path in any way other than Stroke (like     * Fill, close path & Stroke etc). Usually this is used internally.</p>     *     * @param code PDF operators that will close the path     */    void closeBlock(String code) {        if (inText) {            pw.println("ET Q");            // setOrientation(); // fixes Orientation matrix        }        if (inStroke) {            pw.println(code);        }        inStroke = inText = false;    }    /**     * This is unsupported - how do you do this with Vector graphics?     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     * @param dx coordinate     * @param dy coordinate     */    @Override    public void copyArea(int x, int y, int w, int h, int dx, int dy) {        // Hmm...   Probably need to keep track of everything        // that has been drawn so far to get the contents of an area    }    //============ Line operations =======================    /**     * <p>     * This returns a child instance of this Graphics object. As with AWT, the     * affects of using the parent instance while the child exists, is not     * determined.</p>     *     * <p>     * Once complete, the child should be released with it's dispose() method     * which will restore the graphics state to it's parent.</p>     *     * @return Graphics object to render onto the page     */    @Override    public Graphics create() {        closeBlock();        PDFGraphics g = createGraphic(page, pw);        // The new instance inherits a few items        g.clipRectangle = new Rectangle(clipRectangle);        return (Graphics) g;    } // end create()    /**     * This method creates a new instance of the class based on the page and a     * print writer.     *     * @param page the page to attach to     * @param pw the <code>PrintWriter</code> to attach to.     */    protected PDFGraphics createGraphic(PDFPage page,            RawPrintWriter pw) {        PDFGraphics g = new PDFGraphics();        g.init(page, pw);        return g;    }    /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using the current point and (x1,y1) as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *     * @param x1 Second control point     * @param y1 Second control point     * @param x2 Destination point     * @param y2 Destination point     */    public void curveto(double x1, double y1, double x2, double y2) {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "v");        lx = (float) x2;        ly = (float) y2;    }    /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier     * control points.     * <p>     * The new current point is (x3,y3)     *     * @param x1 First control point     * @param y1 First control point     * @param x2 Second control point     * @param y2 Second control point     * @param x3 Destination point     * @param y3 Destination point     */    public void curveto(double x1, double y1, double x2, double y2, double x3, double y3) {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c");        lx = (float) x3;        ly = (float) y3;    }    /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using the current point and (x1,y1) as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *     * @param x1 Second control point     * @param y1 Second control point     * @param x2 Destination point     * @param y2 Destination point     */    public void curveto(int x1, int y1, int x2, int y2) {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "v");        lx = x2;        ly = y2;    }    /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier     * control points.     * <p>     * The new current point is (x3,y3)     *     * @param x1 First control point     * @param y1 First control point     * @param x2 Second control point     * @param y2 Second control point     * @param x3 Destination point     * @param y3 Destination point     */    public void curveto(int x1, int y1, int x2, int y2, int x3, int y3) {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c");        lx = x3;        ly = y3;    }    /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using (x1,y1) and the end point as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *     * @param x1 Second control point     * @param y1 Second control point     * @param x2 Destination point     * @param y2 Destination point     */    public void curveto2(double x1, double y1, double x2, double y2) {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "y");        lx = (float) x2;        ly = (float) y2;    }    // Arcs are horrible and complex. They are at the end of the    // file, because they are the largest. This is because, unlike    // Postscript, PDF doesn't have any arc operators, so we must    // implement them by converting into one or more Bezier curves    // (which is how Postscript does them internally).    /**     * This extension appends a Bezier curve to the path. The curve extends from     * the current point to (x2,y2) using (x1,y1) and the end point as the     * Bezier control points.     * <p>     * The new current point is (x2,y2)     *     * @param x1 Second control point     * @param y1 Second control point     * @param x2 Destination point     * @param y2 Destination point     */    public void curveto2(int x1, int y1, int x2, int y2) {        newPath();        pw.println(cxy(x1, y1) + cxy(x2, y2) + "y");        lx = x2;        ly = y2;    }    /**     * Converts the Java space dimension into pdf.     *     * @param w width     * @param h height     * @return String containing the coordinates in PDF space     */    private String cwh(double w, double h) {        return "" + df.format(w) + " " + df.format(h) + " ";    }    /**     * Converts the Java space dimension into pdf.     *     * @param w width     * @param h height     * @return String containing the coordinates in PDF space     */    private String cwh(int w, int h) {        return cwh((double) w, (double) h);    }    /**     * Converts the Java space coordinates into pdf.     *     * @param x coordinate     * @param y coordinate     * @return String containing the coordinates in PDF space     */    private String cxy(double x, double y) {        return "" + df.format(x) + " " + df.format(y) + " ";    }    /**     * Converts the Java space coordinates into pdf.     *     * @param x coordinate     * @param y coordinate     * @return String containing the coordinates in PDF space     */    private String cxy(int x, int y) {        return cxy((double) x, (double) y);    }    /**     * <p>     * This releases any resources used by this Graphics object. You must use     * this method once finished with it. Leaving it open will leave the PDF     * stream in an inconsistent state, and will produce errors.</p>     *     * <p>     * If this was created with Graphics.create() then the parent instance can     * be used again. If not, then this closes the graphics operations for this     * page when used with PDFJob.</p>     *     * <p>     * When using PDFPage, you can create another fresh Graphics instance, which     * will draw over this one.</p>     *     */    @Override    public void dispose() {        closeBlock();        if (child) {            pw.println("Q");    // restore graphics context        } else {            pw.close(); // close the stream if were the parent        }    }    // *********************************************    // **** Implementation of java.awt.Graphics ****    // *********************************************    //============ Rectangle operations =======================    /**     * @see Graphics2D#draw(Shape)     */    @Override    public void draw(Shape s) {        followPath(s, STROKE);    }    /**     * <p>     * Not implemented</p>     *     * <p>     * Draws a 3-D highlighted outline of the specified rectangle. The edges of     * the rectangle are highlighted so that they appear to be beveled and lit     * from the upper left corner. The colors used for the highlighting effect     * are determined based on the current color. The resulting rectangle covers     * an area that is width + 1 pixels wide by height + 1 pixels tall.     * </p>     *     * @param x an <code>int</code> value     * @param y an <code>int</code> value     * @param width an <code>int</code> value     * @param height an <code>int</code> value     * @param raised a <code>boolean</code> value     */    @Override    public void draw3DRect(int x, int y,            int width, int height, boolean raised) {        // Not implemented    }    /**     * Draws an arc     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     * @param sa Start angle     * @param aa End angle     */    @Override    public void drawArc(int x, int y, int w, int h, int sa, int aa) {        w = w >> 1;        h = h >> 1;        x += w;        y += h;        arc((double) x, (double) y,                (double) w, (double) h,                (double) -sa, (double) (-sa - aa),                false);    }    /**     * <p>     * Not implemented</p>     *     * @param data a <code>byte[]</code> value     * @param offset an <code>int</code> value     * @param length an <code>int</code> value     * @param x an <code>int</code> value     * @param y an <code>int</code> value     */    @Override    public void drawBytes(byte[] data, int offset, int length, int x, int y) {    }    //============ Optimizers =======================    /**     * @see Graphics2D#drawGlyphVector(GlyphVector, float, float)     */    @Override    public void drawGlyphVector(GlyphVector g, float x, float y) {        Shape s = g.getOutline(x, y);        fill(s);    }    /**     * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int)     */    @Override    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {        BufferedImage result = img;        if (op != null) {            result = op.createCompatibleDestImage(img, img.getColorModel());            result = op.filter(img, result);        }        drawImage(result, x, y, null);    }    /**     * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver)     */    @Override    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {        // return drawImage(img, null, xform, null, obs);        return true;    }    /**     * <p>     * Draw's an image onto the page, with a backing colour.</p>     *     * @param img The java.awt.Image     * @param x coordinate on page     * @param y coordinate on page     * @param bgcolor Background colour     * @param obs ImageObserver     * @return true if drawn     */    @Override    public boolean drawImage(Image img, int x, int y, Color bgcolor,            ImageObserver obs) {        return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs),                bgcolor, obs);    }    /**     * Draw's an image onto the page     *     * @param img The java.awt.Image     * @param x coordinate on page     * @param y coordinate on page     * @param obs ImageObserver     * @return true if drawn     */    @Override    public boolean drawImage(Image img, int x, int y, ImageObserver obs) {        return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs);    }    /**     * <p>     * Draw's an image onto the page, with a backing colour.</p>     *     * @param img The java.awt.Image     * @param x coordinate on page     * @param y coordinate on page     * @param w Width on page     * @param h height on page     * @param bgcolor Background colour     * @param obs ImageObserver     * @return true if drawn     */    @Override    public boolean drawImage(Image img, int x, int y, int w, int h,            Color bgcolor, ImageObserver obs) {        closeBlock();        pw.print("q "); // save state        Color c = getColor();        // save current colour        setColor(bgcolor);      // change the colour        drawRect(x, y, w, h);        closeBlock("B Q");               // fill stroke, restore state        paint = c;              // restore original colour        return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs);    }    /**     * <p>     * Draws an image onto the page.</p>     *     * <p>     * This method is implemented with ASCIIbase85 encoding and the zip stream     * deflater. It results in a stream that is anywhere from 3 to 10 times as     * big as the image. This obviously needs some improvement, but it works     * well for small images</p>     *     * @param img The java.awt.Image     * @param x coordinate on page     * @param y coordinate on page     * @param w Width on page     * @param h height on page     * @param obs ImageObserver     * @return true if drawn     */    @Override    public boolean drawImage(Image img, int x, int y, int w, int h,            ImageObserver obs) {        closeBlock();        PDFImage image;        if (usedImages.containsKey(img)) {            image = usedImages.get(img);        } else {            image = new PDFImage(img, x, y, w, h, obs);            // The image needs to be registered in several places            page.getPDFDocument().setImageName(image);            page.getPDFDocument().add(image);            usedImages.put(img, image);        }        page.addToProcset("/ImageC");        page.addImageResource(image.getName() + " " + image.getSerialID()                + " 0 R");        // JM        /*page.addResource("/XObject << " + image.getName() + " " +      image.getSerialID() + " 0 R >>");*/        // q w 0 0 h x y cm % the coordinate matrix        AffineTransform newTransform = new AffineTransform(w, 0, 0, h, x, y);        newTransform.preConcatenate(pTransform);        AffineTransform transformToSet = newTransform;        pw.print("q " + transformToSet.getScaleX() + " "                + transformToSet.getShearY() + " "                + transformToSet.getShearX() + " "                + transformToSet.getScaleY() + " "                + transformToSet.getTranslateX() + " "                + transformToSet.getTranslateY() + " cm \n" + image.getName() + " Do\nQ\n");        return false;    }    /**     * Draw's an image onto the page, with scaling     * <p>     * This is not yet supported.     *     * @param img The java.awt.Image     * @param dx1 coordinate on page     * @param dy1 coordinate on page     * @param dx2 coordinate on page     * @param dy2 coordinate on page     * @param sx1 coordinate on image     * @param sy1 coordinate on image     * @param sx2 coordinate on image     * @param sy2 coordinate on image     * @param bgcolor Background colour     * @param obs ImageObserver     * @return true if drawn     */    @Override    public boolean drawImage(Image img, int dx1, int dy1, int dx2,            int dy2, int sx1, int sy1, int sx2, int sy2,            Color bgcolor, ImageObserver obs) {        return false;    }    //============ Clipping operations =======================    /**     * Draw's an image onto the page, with scaling     * <p>     * This is not yet supported.     *     * @param img The java.awt.Image     * @param dx1 coordinate on page     * @param dy1 coordinate on page     * @param dx2 coordinate on page     * @param dy2 coordinate on page     * @param sx1 coordinate on image     * @param sy1 coordinate on image     * @param sx2 coordinate on image     * @param sy2 coordinate on image     * @param obs ImageObserver     * @return true if drawn     */    @Override    public boolean drawImage(Image img, int dx1, int dy1, int dx2,            int dy2, int sx1, int sy1, int sx2, int sy2,            ImageObserver obs) {        // This shouldn't be too bad, just change the coordinate matrix        return false;    }    /**     * Draws a line between two coordinates.     *     * If the first coordinate is the same as the last one drawn (i.e. a     * previous drawLine, moveto, etc) it is ignored.     *     * @param x1 coordinate     * @param y1 coordinate     * @param x2 coordinate     * @param y2 coordinate     */    @Override    public void drawLine(int x1, int y1, int x2, int y2) {        moveto(x1, y1);        lineto(x2, y2);    }    //============ Arcs operations ==============================    // These are the standard Graphics operators. They use the    // arc extension operators to achieve the affect.    /**     * <p>     * Draws an oval</p>     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void drawOval(int x, int y, int w, int h) {        drawArc(x, y, w, h, 0, 360);    }    /**     * Draws a polygon, linking the first and last coordinates.     *     * @param xp Array of x coordinates     * @param yp Array of y coordinates     * @param np number of points in polygon     */    @Override    public void drawPolygon(int[] xp, int[] yp, int np) {        polygon(xp, yp, np);        closeBlock("s"); // close path and stroke    }    /**     * Draws a polyline. The first and last coordinates are not linked.     *     * @param xp Array of x coordinates     * @param yp Array of y coordinates     * @param np number of points in polyline     */    @Override    public void drawPolyline(int[] xp, int[] yp, int np) {        polygon(xp, yp, np);        // no stroke, as we keep the optimizer in stroke state    }    /**     * We override Graphics.drawRect as it doesn't join the 4 lines. Also, PDF     * provides us with a Rectangle operator, so we will use that.     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void drawRect(int x, int y, int w, int h) {        newPath();        pw.print(cxy(x, y) + cwh(w, h) + "re "); // rectangle        lx = x; // I don't know if this is correct, but lets see if PDF ends        ly = y; // the rectangle at it's start.        // stroke (optimized)    }    /**     * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform)     */    @Override    public void drawRenderableImage(RenderableImage img, AffineTransform xform) {        drawRenderedImage(img.createDefaultRendering(), xform);    }    /**     * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform)     */    @Override    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {        BufferedImage image = null;        if (img instanceof BufferedImage) {            image = (BufferedImage) img;        } else {            ColorModel cm = img.getColorModel();            int width = img.getWidth();            int height = img.getHeight();            WritableRaster raster = cm.createCompatibleWritableRaster(width, height);            boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();            Hashtable<String, Object> properties = new Hashtable<String, Object>();            String[] keys = img.getPropertyNames();            if (keys != null) {                for (int i = 0; i < keys.length; i++) {                    properties.put(keys[i], img.getProperty(keys[i]));                }            }            BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);            img.copyData(raster);            image = result;        }        drawImage(image, xform, null);    }    /**     * This is not yet implemented     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     * @param aw a-width     * @param ah a-height     */    @Override    public void drawRoundRect(int x, int y, int w, int h, int aw, int ah) {    }    //============ Oval operations =======================    /**     * Draws a string using a AttributedCharacterIterator.     * <p>     * This is not supported yet, as I have no idea what an     * AttributedCharacterIterator is.     * <p>     * This method is new to the Java2 API.     */    @Override    public void drawString(java.text.AttributedCharacterIterator aci,            float x, float y) {    }    /**     * Draws a string using a AttributedCharacterIterator.     * <p>     * This is not supported yet, as I have no idea what an     * AttributedCharacterIterator is.     * <p>     * This method is new to the Java2 API.     */    @Override    public void drawString(java.text.AttributedCharacterIterator aci,            int x, int y) {    }    public void drawStringWithMode(String s, float x, float y, int mode) {        newTextBlock(x, y);        if (mode > -1) {            pw.println("" + mode + " Tr");        }        if (pdffont instanceof PDFEmbeddedFont) {            pw.print("[(");            pw.printRaw(PDFStringHelper.makeRawPDFString(s));            pw.println(")] TJ");        } else {            pw.print(PDFStringHelper.makePDFString(s));            pw.println(" Tj");        }        closeBlock();    }    @Override    public void drawString(String s, float x, float y) {        drawStringWithMode(s, x, y, -1);    }    /**     * This draws a string.     *     * @param s     * @oaran s String to draw     * @param x coordinate     * @param y coordinate     */    @Override    public void drawString(String s, int x, int y) {        drawString(s, (float) x, (float) y);    }    public void drawTransparentString(String s, float x, float y) {        drawStringWithMode(s, x, y, 3);    }    /**     * This draws a transparent string.     *     * @oaran s String to draw     * @param x coordinate     * @param y coordinate     */    public void drawTransparentString(String s, int x, int y) {        drawTransparentString(s, (float) x, (float) y);    }    /**     * @see Graphics2D#fill(Shape)     */    @Override    public void fill(Shape s) {        followPath(s, FILL);    }    /**     * <p>     * Not implemented</p>     *     * @param x an <code>int</code> value     * @param y an <code>int</code> value     * @param width an <code>int</code> value     * @param height an <code>int</code> value     * @param raised a <code>boolean</code> value     */    @Override    public void fill3DRect(int x, int y,            int width, int height, boolean raised) {        // Not implemented    }    /**     * Fills an arc, joining the start and end coordinates     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     * @param sa Start angle     * @param aa End angle     */    @Override    public void fillArc(int x, int y, int w, int h, int sa, int aa) {        // here we fool the optimizer. We force any open path to be closed,        // then draw the arc. Finally, as the optimizer hasn't stroke'd the        // path, we close and fill it, and mark the Stroke as closed.        //        // Note: The lineto to the centre of the object is required, because        //       the fill only fills the arc. Skipping this includes an extra        //       chord, which isn't correct. Peter May 31 2000        closeBlock();        patternFill();        drawArc(x, y, w, h, sa, aa);        lineto(x + (w >> 1), y + (h >> 1));        if (shadingFill()) {            return;        }        closeBlock("b"); // closepath and fill    }    //============ Extension operations ==============================    // These are extensions, and provide access to PDF Specific    // operators.    /**     * <p>     * Draws a filled oval</p>     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void fillOval(int x, int y, int w, int h) {        fillArc(x, y, w, h, 0, 360);    }    //============ Polygon operations =======================    /**     * Fills a polygon.     *     * @param xp Array of x coordinates     * @param yp Array of y coordinates     * @param np number of points in polygon     */    @Override    public void fillPolygon(int[] xp, int[] yp, int np) {        closeBlock();    // finish off any previous paths        patternFill();        polygon(xp, yp, np);        if (shadingFill()) {            return;        }        closeBlock("b"); // closepath, fill and stroke    }    //============ Image operations =======================    /**     * Fills a rectangle with the current colour     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void fillRect(int x, int y, int w, int h) {        // end any path & stroke. This ensures the fill is on this        // rectangle, and not on any previous graphics        closeBlock();        patternFill();        drawRect(x, y, w, h);        if (shadingFill()) {            return;        }        closeBlock("B"); // rectangle, fill stroke    }    private void patternFill() {        if (pattern != null) {            pw.println("/Pattern cs");            pw.println(pattern + " scn");        }    }    private boolean shadingFill() {        if (pattern == null && shading != null) {            saveState();            pw.println("W n");            pw.println(shading + " sh");            restoreState();            return true;        }        return false;    }    //============ Round Rectangle operations =======================    /**     * This is not yet implemented     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     * @param aw a-width     * @param ah a-height     */    @Override    public void fillRoundRect(int x, int y, int w, int h, int aw, int ah) {    }    ///////////////////////////////////////////////    //    //    //		implementation specific methods    //    //    private void followPath(Shape s, int drawType) {        PathIterator points;        if (s == null) {            return;        }        if (drawType == FILL) {            patternFill();        }        if (drawType == STROKE) {            if (!(stroke instanceof BasicStroke)) {                s = stroke.createStrokedShape(s);                followPath(s, FILL);                return;            }        }//      if (drawType==STROKE) {//          setStrokeDiff(stroke, oldStroke);//          oldStroke = stroke;//          setStrokePaint();//      }//      else if (drawType==FILL)//          setFillPaint();        points = s.getPathIterator(IDENTITY);        int segments = 0;        float[] coords = new float[6];        while (!points.isDone()) {            segments++;            int segtype = points.currentSegment(coords);            switch (segtype) {                case PathIterator.SEG_CLOSE:                    pw.print("h ");                    break;                case PathIterator.SEG_CUBICTO:                    curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);                    break;                case PathIterator.SEG_LINETO:                    lineto(coords[0], coords[1]);                    break;                case PathIterator.SEG_MOVETO:                    moveto(coords[0], coords[1]);                    break;                case PathIterator.SEG_QUADTO:                    curveto(coords[0], coords[1], coords[2], coords[3]);                    break;            }            points.next();        }        switch (drawType) {            case FILL:                if (segments > 0) {                    if (pattern == null && shading != null) {                        saveState();                        if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) {                            closeBlock("W*");                        } else {                            closeBlock("W");                        }                        pw.println("n");                        pw.println(shading + " sh");                        restoreState();                        return;                    }                    if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) {                        closeBlock("B*");                    } else {                        closeBlock("B");                    }                }                break;            case STROKE:                if (segments > 0) {                    closeBlock("S");                }                break;            case CLIP:            default: //drawType==CLIP                if (segments == 0) {                    drawRect(0, 0, 0, 0);                }                if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) {                    closeBlock("W*");                } else {                    closeBlock("W");                }        }    }    /**     * @see Graphics2D#getBackground()     */    @Override    public Color getBackground() {        return background;    }    /**     * Returns the Shape of the clipping region As my JDK docs say, this may     * break with Java 2D.     *     * @return Shape of the clipping region     */    @Override    public Shape getClip() {        return null;    }    /**     * Returns the Rectangle that fits the current clipping region     *     * @return the Rectangle that fits the current clipping region     */    @Override    public Rectangle getClipBounds() {        return clipRectangle;    }    //============ Color operations =======================    /**     * Returns the current pen Colour     *     * @return the current pen Colour     */    @Override    public Color getColor() {        return (paint instanceof Color) ? (Color) paint : Color.black;    }    /**     * @see Graphics2D#getComposite()     */    @Override    public Composite getComposite() {        return composite;    }    /**     * @see Graphics2D#getDeviceConfiguration()     */    @Override    public GraphicsConfiguration getDeviceConfiguration() {        return dg2.getDeviceConfiguration();    }    /**     * Return's the current font.     *     * @return the current font.     */    @Override    public Font getFont() {        if (font == null) {            setFont(new Font("SansSerif", Font.PLAIN, 12));        }        return font;    }    /**     * Returns the FontMetrics for a font.     * <p>     * This doesn't work correctly. Perhaps having some way of mapping the base     * 14 fonts to our own FontMetrics implementation?     *     * @param font The java.awt.Font to return the metrics for     * @return FontMetrics for a font     */    @Override    public FontMetrics getFontMetrics(Font font) {        Frame dummy = new Frame();        dummy.addNotify();        Image image = dummy.createImage(100, 100);        if (image == null) {            System.err.println("getFontMetrics: image is null");        }        Graphics graphics = image.getGraphics();        return graphics.getFontMetrics(font);    }    /**     * @see Graphics2D#getFontRenderContext()     */    @Override    public FontRenderContext getFontRenderContext() {        boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING));        boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS));        return new FontRenderContext(new AffineTransform(), antialias, fractions);    }    /**     * Returns the associated PDFPage for this graphic     *     * @return the associated PDFPage for this graphic     */    public PDFPage getPage() {        return page;    }    /**     * Returns the current pen Colour     *     * @return the current pen Colour     */    @Override    public Paint getPaint() {        return paint;    }    /**     * @param arg0 a key     * @return the rendering hint     */    @Override    public Object getRenderingHint(Key arg0) {        return rhints.get(arg0);    }    /**     * @see Graphics2D#getRenderingHints()     */    @Override    public RenderingHints getRenderingHints() {        return rhints;    }    /**     * @see Graphics2D#getStroke()     */    @Override    public Stroke getStroke() {        return stroke;    }    /**     * @see Graphics2D#getTransform()     */    @Override    public AffineTransform getTransform() {        return new AffineTransform(transform);    }    /**     * Returns the PrintWriter handling the underlying stream     *     * @return the PrintWriter handling the underlying stream     */    public RawPrintWriter getWriter() {        return pw;    }    /**     * @see Graphics2D#hit(Rectangle, Shape, boolean)     */    @Override    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {        if (onStroke) {            s = stroke.createStrokedShape(s);        }        Area area = new Area(s);        if (clip != null) {            area.intersect(clip);        }        return area.intersects(rect.x, rect.y, rect.width, rect.height);    }    /**     * This initialises the stream by saving the current graphics state, and     * setting up the default line width (for us).     *     * It also sets up the instance ready for graphic operations and any     * optimisations.     *     * <p>     * For child instances, the stream is already open, so this should keep     * things happy.     */    private void init() {        PageFormat pf = page.getPageFormat();        // save graphics state (restored by dispose)        if (child) {            pw.print("q ");        }        // now initialise the instance        //setColor(Color.black);        paint = Color.black;        // possible: if parent.color is not black, then force black?        // must check to see what AWT does?        // Original User Space Transform (identity)        // Transform from Java Space to PDF Space        pTransform = new AffineTransform();        pTransform.translate(0, pf.getHeight());        pTransform.scale(1d, -1d);        // Combined Transform User->Java->PDF        setNewTranform(new AffineTransform());        // Set the line width        setStroke(DEF_STROKE);    }    /**     * This is called by PDFPage when creating a Graphcis instance.     *     * @param page The PDFPage to draw onto.     */    protected void init(PDFPage page) {        this.page = page;        // We are the parent instance        child = false;        // Now create a stream to store the graphics in        PDFStream stream = new PDFStream();        // To view detail in uncompressed format comment out the next line        stream.setDeflate(true);        page.getPDFDocument().add(stream);        page.add(stream);        pw = new RawPrintWriter(stream.getOutputStream());        // initially, we are limited to the page size        clipRectangle = page.getImageableArea();        // finally initialise the stream        init();    }    /**     * This method is used internally by create() and by the PDFJob class     *     * @param page PDFPage to draw into     * @param pw PrintWriter to use     */    protected void init(PDFPage page, RawPrintWriter pw) {        this.page = page;        this.pw = pw;        // In this case, we didn't create the stream (our parent did)        // so child is true (see dispose)        child = true;        // finally initialise the stream        init();    }    /**     * This adds a line segment to the current path     *     * @param x coordinate     * @param y coordinate     */    public void lineto(double x, double y) {        newPath();        // no optimisation here as it may introduce errors on decimal coordinates.        pw.print(cxy(x, y) + "l ");        lx = (float) x;        ly = (float) y;    }    /**     * This adds a line segment to the current path     *     * @param x coordinate     * @param y coordinate     */    public void lineto(int x, int y) {        newPath();        if (lx != x && ly != y) {            pw.print(cxy(x, y) + "l ");        }        lx = x;        ly = y;    }    /**     * This moves the current drawing point.     *     * @param x coordinate     * @param y coordinate     */    public void moveto(double x, double y) {        newPath();        // no optimisation here as it may introduce errors on decimal coordinates.        pw.print(cxy(x, y) + "m ");        lx = (float) x;        ly = (float) y;    }    /**     * This moves the current drawing point.     *     * @param x coordinate     * @param y coordinate     */    public void moveto(int x, int y) {        newPath();        if (lx != x || ly != y) {            pw.print(cxy(x, y) + "m ");        }        lx = x;        ly = y;    }    /**     * Functions that draw lines should start by calling this. It starts a new     * path unless inStroke is set, in that case it uses the existing path     */    void newPath() {        if (inText) {            closeBlock();        }        if (!inStroke) {            if (pre_np != null) {                pw.print(pre_np);       // this is the prefix set by setOrientation()                pre_np = null;            }            pw.print("n ");        }        inText = false;        inStroke = true;        // an unlikely coordinate to fool the moveto() optimizer        lx = ly = -9999;    }    /**     * <p>     * Functions that draw text should start by calling this. It starts a text     * block (accounting for media orientation) unless we are already in a Text     * block.</p>     *     * <p>     * It also handles if the font has been changed since the current text block     * was started, so your function will be current.</p>     *     * @param x x coordinate in java space     * @param y y coordinate in java space     */    void newTextBlock(float x, float y) {        // close the current path if there is one        if (inStroke) {            closeBlock();        }        // create the text block if one is not current. If we are, the newFont        // condition at the end catches font changes        if (!inText) {            // This ensures that there is a font available            getFont();            pw.print("q BT ");            tx = ty = 0;            AffineTransform tm = new AffineTransform(pTransform);            pw.println("" + df.format(tm.getScaleX()) + " "                    + "" + df.format(tm.getShearY()) + " "                    + "" + df.format(tm.getShearX()) + " "                    + "" + df.format(tm.getScaleY()) + " "                    + "" + df.format(tm.getTranslateX()) + " "                    + "" + df.format(tm.getTranslateY()) + " Tm"            );            // produce the text matrix for the media//      switch(mediaRot) {//      case PageFormat.PORTRAIT: // Portrait//        //pw.println("1 0 0 1 0 0 Tm");//        break;////      case PageFormat.LANDSCAPE:        // Landscape//        pw.println("0 1 -1 0 0 0 Tm");      // rotate//        break;////      case 180:       // Inverted Portrait//        pw.println("1 0 0 -1 0 0 Tm");//        break;////      case PageFormat.REVERSE_LANDSCAPE:       // Seascape//        pw.println("0 -1 1 0 0 0 Tm");      // rotate//        break;//      }            // move the text cursor by an absolute amount            pw.print(txy(x, y) + "Td ");        } else {            // move the text cursor by a relative amount            pw.print(twh(x, y, tx, ty) + "Td ");            //pw.print(txy(x,y)+"Td ");        }        // preserve the coordinates for the next time        tx = x;        ty = y;        if (newFont || !inText) {            pw.print(pdffont.getName() + " " + font.getSize() + " Tf ");        }        // later add colour changes here (if required)        inStroke = newFont = false;        inText = true;    }    /**     * This is used to add a polygon to the current path. Used by drawPolygon(),     * drawPolyline() and fillPolygon() etal     *     * @param xp Array of x coordinates     * @param yp Array of y coordinates     * @param np number of points in polygon     * @see #drawPolygon     * @see #drawPolyline     * @see #fillPolygon     */    public void polygon(int[] xp, int[] yp, int np) {        // newPath() not needed here as moveto does it ;-)        moveto(xp[0], yp[0]);        for (int i = 1; i < np; i++) {            lineto(xp[i], yp[i]);        }    }    /**     * @see Graphics2D#rotate(double)     */    @Override    public void rotate(double theta) {        AffineTransform newTransform = new AffineTransform(transform);        newTransform.rotate(theta);        setNewTranform(newTransform);    }    /**     * @see Graphics2D#rotate(double, double, double)     */    @Override    public void rotate(double theta, double x, double y) {        AffineTransform newTransform = new AffineTransform(transform);        newTransform.rotate(theta, x, y);        setNewTranform(newTransform);    }    /**     * @see Graphics2D#scale(double, double)     */    @Override    public void scale(double sx, double sy) {        AffineTransform newTransform = new AffineTransform(transform);        newTransform.scale(sx, sy);        setNewTranform(newTransform);    }    /**     * @see Graphics2D#setBackground(Color)     */    @Override    public void setBackground(Color color) {        background = color;    }    /**     * Clips to a set of coordinates     *     * @param x coordinate     * @param y coordinate     * @param w width     * @param h height     */    @Override    public void setClip(int x, int y, int w, int h) {        clipRectangle = new Rectangle(x, y, w, h);        closeBlock();            // finish off any existing paths        drawRect(x, y, w, h);        closeBlock("W n");               // clip to current path    }    /**     * As my JDK docs say, this may break with Java 2D.     * <p>     * Sets the clipping region to that of a Shape.     *     * @param s Shape to clip to.     */    @Override    public void setClip(Shape s) {        Rectangle r = s.getBounds();        setClip(r.x, r.y, r.width, r.height);    }    /**     * Sets the color for drawing     *     * @param c Color to use     */    @Override    public void setColor(Color c) {        setPaint(c);    }    /**     * @see Graphics2D#setComposite(Composite)     */    @Override    public void setComposite(Composite comp) {        this.composite = comp;    }    /**     * This extension sets the line width to the default of 1mm which is what     * Java uses when drawing to a PrintJob.     */    public void setDefaultLineWidth() {        closeBlock(); // draw any path before we change the line width        pw.println("1 w");    }    /**     * This sets the font.     *     * @param f java.awt.Font to set to.     */    @Override    public void setFont(Font f) {        // optimize: Save some space if the font is already the current one.        if (font != f) {            font = f;            pdffont = page.getFont("/Type1", f.getName(), f.getStyle());            // mark the font as changed            newFont = true;        }    }    public void setExistingTtfFont(Font f) {        if (font != f) {            font = f;            pdffont = page.getFont("/TrueType", f.getName(), f.getStyle());            // mark the font as changed            newFont = true;        }    }    public void setTtfFont(Font f, File file) throws IOException {        if (font != f) {            font = f;            pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file);            // mark the font as changed            newFont = true;        }    }    private void setLineCap(int cap) {        int lineCap = 0;        switch (cap) {            case BasicStroke.JOIN_MITER:                lineCap = 0;                break;            case BasicStroke.JOIN_ROUND:                lineCap = 1;                break;            case BasicStroke.JOIN_BEVEL:                lineCap = 2;                break;        }        if (this.lineCap != lineCap) {            closeBlock(); // draw any path before we change the line width            this.lineCap = lineCap;            pw.println("" + lineCap + " J");        }    }    private void setLineJoin(int join) {        int lineJoin = 0;        switch (join) {            case BasicStroke.JOIN_MITER:                lineJoin = 0;                break;            case BasicStroke.JOIN_ROUND:                lineJoin = 1;                break;            case BasicStroke.JOIN_BEVEL:                lineJoin = 2;                break;        }        if (this.lineJoin != lineJoin) {            closeBlock(); // draw any path before we change the line width            this.lineJoin = lineJoin;            pw.println("" + lineJoin + " j");        }    }    /**     * This extension allows the width of the drawn line to be set     *     * @param width Line width in pdf graphic units (points)     */    public void setLineWidth(float width) {        if (width != this.lineWidth) {            closeBlock(); // draw any path before we change the line width            this.lineWidth = width;            pw.println("" + width + " w");        }    }    private void setMiterLimit(float limit) {        if (limit != this.miterLimit) {            closeBlock(); // draw any path before we change the line width            this.miterLimit = limit;            pw.println("" + limit + " M");        }    }    /**     * Sets the paint for drawing     *     * @param paint Paint to use     */    @Override    public void setPaint(Paint paint) {        this.paint = paint;        this.shading = null;        this.pattern = null;        if (paint instanceof Color) {            Color c = (Color) paint;            double r = ((double) c.getRed()) / 255.0;            double g = ((double) c.getGreen()) / 255.0;            double b = ((double) c.getBlue()) / 255.0;            closeBlock(); // This ensures any paths are drawn in the previous            // colours            pw.println("" + r + " " + g + " " + b + " rg "                    + r + " " + g + " " + b + " RG");        }        if (paint instanceof MultipleGradientPaint) {            closeBlock();            MultipleGradientPaint grad = (MultipleGradientPaint) paint;            if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) {                LinearGradientPaint linGrad = (LinearGradientPaint) grad;                Point2D start = linGrad.getStartPoint();                Point2D end = linGrad.getEndPoint();                double deltaX = end.getX() - start.getX();                double deltaY = end.getY() - start.getY();                Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY);                int colorCount = grad.getFractions().length;                float fractions2[] = new float[colorCount * 2 - 1];                Color colors2[] = new Color[colorCount * 2 - 1];                for (int i = 0; i < colorCount; i++) {                    colors2[i] = linGrad.getColors()[i];                    fractions2[i] = linGrad.getFractions()[i] / 2;                }                for (int i = 0; i < colorCount; i++) {                    colors2[colors2.length - i - 1] = linGrad.getColors()[i];                    fractions2[colors2.length - i - 1] = 1f - linGrad.getFractions()[i] / 2;                }                LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT);                grad = linGrad2;            }            List<String> functions2Refs = new ArrayList<>();            for (int i = 1; i < grad.getColors().length; i++) {                final Color color1 = grad.getColors()[i - 1];                final Color color2 = grad.getColors()[i];                PDFObject function2 = new PDFObject(null) {                    @Override                    public void write(OutputStream os) throws IOException {                        writeStart(os);                        os.write(("/FunctionType 2 /Domain [0 1] /C0 ["                                + (((float) color1.getRed()) / 255.0f) + " "                                + (((float) color1.getGreen()) / 255.0f) + " "                                + (((float) color1.getBlue()) / 255.0f) + "] /C1 ["                                + (((float) color2.getRed()) / 255.0f) + " "                                + (((float) color2.getGreen()) / 255.0f) + " "                                + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes());                        writeEnd(os);                    }                };                page.getPDFDocument().add(function2);                functions2Refs.add(function2.getSerialID() + " 0 R");            }            final MultipleGradientPaint fgrad = grad;            PDFObject function3 = new PDFObject(null) {                @Override                public void write(OutputStream os) throws IOException {                    writeStart(os);                    os.write(("/FunctionType 3 /Domain [0 1] /Functions [" + String.join(" ", functions2Refs) + "] ").getBytes());                    int lastcols = fgrad.getColors().length - 1;                    List<String> bounds = new ArrayList<>();                    List<String> encode = new ArrayList<>();                    for (int i = 1; i < fgrad.getColors().length; i++) {                        if (i < lastcols) {                            bounds.add("" + fgrad.getFractions()[i]);                        }                        encode.add("0 1");                    }                    os.write(("/Bounds [" + String.join(" ", bounds) + "] ").getBytes());                    os.write(("/Encode [" + String.join(" ", encode) + "]\n").getBytes());                    writeEnd(os);                }            };            page.getPDFDocument().add(function3);            PDFObject shading = new PDFObject(null) {                @Override                public void write(OutputStream os) throws IOException {                    writeStart(os);                    if (fgrad instanceof LinearGradientPaint) {                        LinearGradientPaint linGrad = (LinearGradientPaint) fgrad;                        MyDoubleRect coords;                        if (linGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) {                            coords = new MyDoubleRect(linGrad.getStartPoint().getX(),                                    linGrad.getStartPoint().getY(),                                    linGrad.getEndPoint().getX(),                                    linGrad.getEndPoint().getY());                        } else {                            double deltaX = linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX();                            double deltaY = linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY();                            double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);                            coords = new MyDoubleRect(0, 0, 0, len);                        }                        os.write(("/ShadingType 2 /ColorSpace /DeviceRGB "                                + "/Coords [" + coords.xMin + " " + coords.yMin + " " + coords.xMax + " " + coords.yMax + "] "                                + "/Domain [0 1] "                                + "/Function " + function3.getSerialID() + " 0 R /Extend [true true]\n").getBytes());                        writeEnd(os);                    }                    if (fgrad instanceof RadialGradientPaint) {                        RadialGradientPaint radGrad = (RadialGradientPaint) fgrad;                        os.write(("/ShadingType 3 /ColorSpace /DeviceRGB "                                + "/Coords ["                                + radGrad.getFocusPoint().getX() + " "                                + radGrad.getFocusPoint().getY() + " "                                + "0 "                                + radGrad.getCenterPoint().getX() + " "                                + radGrad.getCenterPoint().getY() + " "                                + radGrad.getRadius()                                + "] "                                + "/Domain [0 1] "                                + "/Function " + function3.getSerialID() + " 0 R /Extend [true true]\n").getBytes());                        writeEnd(os);                    }                }            };            page.getPDFDocument().add(shading);            /*PDFObject pattern = new PDFObject("/Pattern") {                @Override                public void write(OutputStream os) throws IOException {                    writeStart(os);                    os.write(("/PatternType 2 /Shading " + shading.getSerialID() + " 0 R\n").getBytes());                    writeEnd(os);                }            };            page.getPDFDocument().add(pattern);*/            shadingCount++;            if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) {                LinearGradientPaint linGrad = (LinearGradientPaint) grad;                double deltaX = linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX();                double deltaY = linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY();                double tana = deltaX / deltaY;                double alfa = Math.atan(tana);                double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY);                AffineTransform m = AffineTransform.getRotateInstance(alfa);                String matrixStr = "" + m.getScaleX() + " "                        + m.getShearY() + " " + m.getShearX() + " " + m.getScaleY() + " "                        + m.getTranslateX() + " " + m.getTranslateY();                PDFStream innerPattern = new PDFStream("/Pattern") {                    @Override                    public void write(OutputStream os) throws IOException {                        writeStart(os);                        double w = Math.abs(linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX());                        double h = Math.abs(linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY());                        if (w < 1) {                            w = 1;                        }                        if (h < 1) {                            h = 1;                        }                        w = 1;                        h = len;                        os.write("/PatternType 1\n".getBytes());                        os.write("/PaintType 1\n".getBytes());                        os.write("/TilingType 2\n".getBytes());                        os.write(("/BBox [0 0 " + w                                + " " + h + "]\n").getBytes());                        os.write(("/XStep " + w + "\n").getBytes());                        os.write(("/YStep " + h + "\n").getBytes());                        os.write(("/Resources << /Font << " + pdffont.getName() + " " + pdffont.getSerialID() + " 0 R >> "                                // + "/Pattern << /pin" + shadingCount + " " + pattern.getSerialID() + " 0 R >>"                                + "/Shading << /Shin" + shadingCount + " " + shading.getSerialID() + " 0 R >>" + ">>\n").getBytes());                        //"1 0 0 1 0 0"                        os.write(("/Matrix [" + matrixStr + "]\n").getBytes());                        writeStream(os);                    }                };                OutputStream patOs = innerPattern.getOutputStream();                try {                    patOs.write(("/Shin" + shadingCount + " sh").getBytes());                } catch (IOException ex) {                    Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex);                }                page.getPDFDocument().add(innerPattern);                page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R");                this.pattern = "/p" + shadingCount;            }            //page.addPatternResource("/p" + shadingCount + " " + pattern.getSerialID() + " 0 R");            page.addShadingResource("/Sh" + shadingCount + " " + shading.getSerialID() + " 0 R ");            this.shading = "/Sh" + shadingCount;        }    }    /**     * Not implemented, as this is not supported in the PDF specification.     */    @Override    public void setPaintMode() {    }    /**     * Sets a rendering hint     *     * @param arg0     * @param arg1     */    @Override    public void setRenderingHint(Key arg0, Object arg1) {        if (arg1 != null) {            rhints.put(arg0, arg1);        } else {            rhints.remove(arg0);        }    }    // Add Graphics2D methods.    /**     * @see Graphics2D#setRenderingHints(Map)     */    @Override    public void setRenderingHints(Map<?, ?> hints) {        rhints.clear();        rhints.putAll(hints);    }    /**     * @see Graphics2D#setStroke(Stroke)     */    @Override    public void setStroke(Stroke s) {        this.stroke = s;        if (stroke instanceof BasicStroke) {            BasicStroke bs = (BasicStroke) stroke;            setLineCap(bs.getEndCap());            setLineJoin(bs.getLineJoin());            setLineWidth(bs.getLineWidth());            setMiterLimit(bs.getMiterLimit());            // TODO: Line dash pattern        }    }    /**     * @see Graphics2D#setTransform(AffineTransform)     */    @Override    public void setTransform(AffineTransform t) {        setNewTranform(new AffineTransform(t));    }    /**     * Not implemented, as this is not supported in the PDF specification.     *     * @param c1 Color to xor with     */    @Override    public void setXORMode(Color c1) {    }    //============ Text operations =======================    /**     * @see Graphics2D#shear(double, double)     */    @Override    public void shear(double shx, double shy) {        AffineTransform newTransform = new AffineTransform(transform);        newTransform.shear(shx, shy);        setNewTranform(newTransform);    }    /**     * @see Graphics2D#transform(AffineTransform)     */    @Override    public void transform(AffineTransform tx) {        AffineTransform newTransform = new AffineTransform(transform);        newTransform.concatenate(tx);        setNewTranform(newTransform);    }    /**     * @see Graphics2D#translate(double, double)     */    @Override    public void translate(double tx, double ty) {        AffineTransform newTransform = new AffineTransform(transform);        newTransform.translate(tx, ty);        setNewTranform(newTransform);    }    /**     * @see Graphics#translate(int, int)     */    @Override    public void translate(int x, int y) {        translate((double) x, (double) y);    }    /**     * Converts the Java space coordinates into pdf text space.     *     * @param x coordinate     * @param y coordinate     * @param tx coordinate     * @param ty coordinate     * @return String containing the coordinates in PDF text space     */    private String twh(float x, float y, float tx, float ty) {        float nx = x, ny = y;        float ntx = tx, nty = ty;        nx = (float) (x - tx);        ny = (float) (y - ty);        return "" + df.format(nx) + " " + df.format(ny) + " ";    }    /**     * Converts the Java space coordinates into pdf text space.     *     * @param x coordinate     * @param y coordinate     * @return String containing the coordinates in PDF text space     */    private String txy(float x, float y) {        Point2D ptSrc = new Point2D.Float(x, y);        Point2D ptDst = new Point2D.Float();        pTransform.transform(ptSrc, ptDst);        return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " ";    }    private void setNewTranform(AffineTransform t) {        closeBlock();        AffineTransform newTransform = new AffineTransform(t);        AffineTransform transformToSet = new AffineTransform(newTransform);        if (transform != null) {            AffineTransform realTransformWithPTransform = new AffineTransform(transform);            realTransformWithPTransform.preConcatenate(pTransform);            AffineTransform inverted = new AffineTransform(realTransformWithPTransform);            try {                inverted.invert();            } catch (NoninvertibleTransformException ex) {                Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex);            }            AffineTransform newTransformWithPTransform = new AffineTransform(newTransform);            newTransformWithPTransform.preConcatenate(pTransform);            transformToSet = newTransformWithPTransform;            transformToSet.preConcatenate(inverted);        } else {            transformToSet.preConcatenate(pTransform);        }        transform = newTransform;        pw.println("" + df.format(transformToSet.getScaleX()) + " "                + "" + df.format(transformToSet.getShearY()) + " "                + "" + df.format(transformToSet.getShearX()) + " "                + "" + df.format(transformToSet.getScaleY()) + " "                + "" + df.format(transformToSet.getTranslateX()) + " "                + "" + df.format(transformToSet.getTranslateY()) + " cm"        );    }    private void saveState() {        pw.println("q");    }    private void restoreState() {        pw.println("Q");    }} // end class PDFGraphics